package com.atguigu.gmall.payment.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.atguigu.gmall.common.constant.RabbitConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.model.enums.OrderStatus;
import com.atguigu.gmall.model.enums.PaymentStatus;
import com.atguigu.gmall.model.enums.PaymentType;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.model.payment.PaymentInfo;
import com.atguigu.gmall.order.OrderFeignClient;
import com.atguigu.gmall.payment.config.AlipayConfig;
import com.atguigu.gmall.payment.service.api.PaymentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

/**
 * @author: 封捷
 * @create-date: 2022/9/14 9:29
 */
// 为什么不使用 @RestController 注解？
// 因为同步回调的方法的返回值是："redirect:" + AlipayConfig.return_order_url
// 而 “redirect:” 是 SpringMVC 跳转页面的指令，这个字符串本身并不是响应体
// 所以同步回调的 handler 方法不能加 @ResponseBody 注解，
// 所以类上不能加 @RestController 注解，
// 所以这个类中具体哪一个方法需要加 @ResponseBody 注解的，就单独加
@Controller
@RequestMapping("/api/payment/alipay")
public class PayApiController {

    @Autowired
    private OrderFeignClient orderFeignClient;

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private AlipayClient alipayClient;

    @Autowired
    private RabbitService rabbitService;

    /**
     * 检查是否满足支付条件
     * 保存 PaymentInfo 对象
     * 给支付宝发请求
     * 解析支付宝返回的响应数据
     * 把支付宝返回的想要数据作为当前请求的响应体
     *
     * @param orderId 订单 id
     * @return 满足支付条件：支付宝提供的二维码页面的源代码
     * 不满足支付条件：字符串形式的提示消息
     */
    @ResponseBody
    @GetMapping("/submit/{orderId}")
    public String alipaySubmitToCodePage(
            @PathVariable("orderId") Long orderId) throws AlipayApiException {

        // 一、检查订单是否可以支付
        // 1、根据 orderId 查询 OrderInfo 对象
        OrderInfo orderInfo = orderFeignClient.getOrderInfoById(orderId).getData();

        // 2、通过 OrderInfo 对象获取订单状态
        String orderStatus = orderInfo.getOrderStatus();

        // 3、检查订单状态
        // ※说明点1：访问枚举类型属性 OrderStatus.UNPAID 是 OrderStatus 类型，
        // 而 orderInfo.getOrderStatus() 返回的是 String 类型，二者类型不一致
        // 如果将不一致的两个类型放在一起比较，那么本来业务数据是相等的，也会判定为不相等
        // 所以需要调用 name() 方法，获取枚举类型的字符串类型的属性值
        // 都是 String 类型，然后再比较
        // ※说明点2：对于订单状态来说，只有 UNPAID 这一个状态可以支付，
        // 除此之外所有状态都不能支付。
        // 所以这里我们排除所有不能支付的情况，后续的情况就都是可以支付的了，
        // 避免分支条件的嵌套
        if (!OrderStatus.UNPAID.name().equals(orderStatus)) {
            return "当前[订单]无法支付！";
        }

        // 二、检查 PaymentInfo 是否可以支付
        // 1、设定支付方式
        // ※说明：按说是应该由页面传过来，但是目前咱们项目只支持支付宝支付，所以这里暂时写死了
        String paymentType = PaymentType.ALIPAY.name();

        // 2、根据 orderId 和支付方式尝试到数据库查询 PaymentInfo 对象
        PaymentInfo paymentInfo = paymentService.getPaymentInfoByIdAndType(orderId, paymentType);

        // 3、判断 PaymentInfo 对象是否存在
        if (paymentInfo == null) {
            // 4、如果 PaymentInfo 对象不存在，
            // 那么就保存到数据库并返回 Service 方法中构造的对象
            // 赋值给 paymentInfo 变量，那么 paymentInfo 变量就保证指向一个有效的对象（不是 null 了）
            paymentInfo = paymentService.savePaymentInfo(orderInfo, paymentType);
        }

        // 5、获取 PaymentInfo 对象的状态
        String paymentStatus = paymentInfo.getPaymentStatus();

        // 6、判断 PaymentInfo 对象的状态
        if (!PaymentStatus.UNPAID.name().equals(paymentStatus)) {
            return "当前[交易记录]无法支付！";
        }

        // 三、给支付宝发送请求
        // 1、创建对接支付宝 alipay.trade.page.pay 接口的请求对象
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();

        // 2、设定同步回调地址
        alipayRequest.setReturnUrl(AlipayConfig.return_payment_url);

        // 3、设定异步回调地址
        alipayRequest.setNotifyUrl(AlipayConfig.notify_payment_url);

        // 4、封装要发送给支付宝的参数
        // [1]创建 Map 对象用于封装请求参数
        Map<String, Object> argumentsMap = new HashMap<>();

        // [2]在 Map 中存入所需参数
        // ●商户订单号
        argumentsMap.put("out_trade_no", orderInfo.getOutTradeNo());

        // ●订单总金额
        argumentsMap.put("total_amount", orderInfo.getTotalAmount());

        // ●订单标题
        argumentsMap.put("subject", orderInfo.getTradeBody());

        // ●销售产品码：在电脑支付场景下是固定值
        argumentsMap.put("product_code", "FAST_INSTANT_TRADE_PAY");

        // [3]把 Map 类型对象转换为 JSON 字符串
        String argumentsJSON = JSON.toJSONString(argumentsMap);

        // [4]将 JSON 字符串传入请求对象
        alipayRequest.setBizContent(argumentsJSON);

        // 四、把页面数据作为响应体返回
        // ※说明：getBody()方法获取支付宝返回的响应数据中的响应体部分
        // 同时这个响应体正好就是我们显示二维码页面的响应体
        // 所以我们直接把这个响应体作为当前请求的响应体即可
        // 配合 @ResponseBody 注解即可实现这个效果
        return alipayClient.pageExecute(alipayRequest).getBody();
    }

    @GetMapping("/callback/return")
    public String synchronizeCallBack() {
        return "redirect:" + AlipayConfig.return_order_url;
    }

    // 方法的返回值就是响应体，所以需要加 @ResponseBody
    @ResponseBody
    @PostMapping("/callback/notify")
    public String asynchronousCallBack(
            // 获取封装了全部请求参数的 Map
            @RequestParam Map<String, String> paramsMap) throws AlipayApiException {

        System.out.println("asynchronousCallBack() 方法执行 异步回调");

        // 一、验签
        // 1、执行验签
        boolean signVerified = AlipaySignature.rsaCheckV1(
                paramsMap,
                AlipayConfig.alipay_public_key,
                AlipayConfig.charset,
                AlipayConfig.sign_type);

        // 2、判断验签的结果
        if(!signVerified) {
            // TODO 验签失败则记录异常日志，并在response中返回failure.
            return "failure";
        }

        // TODO 验签成功后，
        //  按照支付结果异步通知中的描述，
        //  对支付结果中的业务内容进行二次校验，
        //  校验成功后在response中返回success并继续商户自身业务处理，
        //  校验失败返回failure

        // 3、获取交易状态
        String tradeStatus = paramsMap.get("trade_status");

        // 4、检查交易状态
        // ※说明：支付宝发送过来的交易状态，至少应该是下面两种情况中的一种，才能判断交易成功
        if (!"TRADE_SUCCESS".equals(tradeStatus) && !"TRADE_FINISHED".equals(tradeStatus)) {
            return "failure";
        }

        // 5、检查商户系统中本地数据交易状态
        // ※说明：这里为什么要检查我们自己的本地数据？
        // 答：是因为防止用户在其它浏览器已经支付完成或其它原因导致交易状态关闭。
        // 其实这样的情况几率非常低，我们只是增加一些保险系数而已
        // [1]获取当前操作的订单编号
        String outTradeNo = paramsMap.get("out_trade_no");

        // [2]查询 PaymentInfo 对象
        PaymentInfo paymentInfo = paymentService.getPaymentInfoByOutTradeNo(outTradeNo, PaymentType.ALIPAY.name());

        // [3]检查 PaymentInfo 对象交易状态
        String paymentStatus = paymentInfo.getPaymentStatus();
        if (PaymentStatus.ClOSED.name().equals(paymentStatus) || PaymentStatus.PAID.name().equals(paymentStatus)) {
            return "failure";
        }

        // 6、在商户系统中更改本地数据交易状态，保存异步回调传过来的相关数据
        paymentService.updatePaymentInfoByCallback(outTradeNo, PaymentType.ALIPAY.name(), paramsMap);

        // ※发送消息：通知订单模块把订单状态修改为已支付
        rabbitService.sendMessage(
                RabbitConst.EXCHANGE_DIRECT_PAYMENT_PAY,
                RabbitConst.ROUTING_PAYMENT_PAY,
                paymentInfo.getOrderId());

        // 7、最终返回 success 完成异步回调
        return "success";
    }

    @ResponseBody
    @RequestMapping("/refund/{orderId}")
    public Result<Void> refund(@PathVariable("orderId") Long orderId) throws AlipayApiException {

        // 1、根据 orderId 和支付方式查询 PaymentInfo 对象
        PaymentInfo paymentInfo = paymentService.getPaymentInfoByIdAndType(orderId, PaymentType.ALIPAY.name());

        // 2、创建请求对象
        AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();

        // 3、封装请求参数
        JSONObject bizContent = new JSONObject();

        // 支付宝平台的交易号
        bizContent.put("trade_no", paymentInfo.getTradeNo());

        // 退款金额。单位是：元
        // ※说明：微信支付金额单位是分
        bizContent.put("refund_amount", paymentInfo.getTotalAmount());

        // 退款原因
        bizContent.put("refund_reason", "手机太便宜了，不够贵");

        request.setBizContent(bizContent.toString());

        // 4、发送请求，接收响应
        AlipayTradeRefundResponse response = alipayClient.execute(request);

        // 5、判断响应结果
        if(response.isSuccess()){

            paymentInfo.setPaymentStatus(PaymentStatus.ClOSED.name());

            paymentService.updatePaymentInfoStatus(paymentInfo);
            System.out.println("调用成功");

            return Result.ok();
        } else {
            System.out.println("调用失败");
            return Result.fail();
        }

    }

    @ResponseBody
    @GetMapping("/query/ali/pay/trade/{outTradeNo}")
    public Result<Boolean> queryAliPayTradeExists(@PathVariable("outTradeNo") String outTradeNo) {
        return Result.ok(paymentService.queryAliPayTradeExists(outTradeNo));
    }

    @ResponseBody
    @GetMapping("/close/ali/pay/trade/{outTradeNo}")
    public Result<Boolean> closeAliPayTrade(@PathVariable("outTradeNo") String outTradeNo) {
        return Result.ok(paymentService.closeAliPayTrade(outTradeNo));
    }

    @ResponseBody
    @GetMapping("/get/payment/info/{outTradeNo}")
    public Result<PaymentInfo> getPaymentInfoByOutTradeNo(@PathVariable("outTradeNo") String outTradeNo) {
        return Result.ok(paymentService.getPaymentInfoByOutTradeNo(outTradeNo, PaymentType.ALIPAY.name()));
    }
}
